
// 入口 service_worker.js 判断并打开background.html页面

// background.html执行index.js文件
// 1、定义nativeMessage
// 2、nativeMessage收到命令
// 3、插件首先会收到初始化的命令
// 4、插件将初始化data，background内容注入到index.js，遍历页面注入contentScript内容
// 5、基础命令在index.js、其他命令在background.js
// 6、其他命令会有重试机制，执行window.rpaActionDispatcher
// 7、window.rpaActionDispatcher会根据命令执行对应的handler
// 8、handler会通过executeFrameAction方法执行contentScript脚本操作dom
// 9、window.nativePort.postMessage 返回消息




console.log('background开始执行')
window.aa = 'aa'
const __DEV__ = true
class PluginBg {


    backgroundCache = `
    window.aa='bb'
class Bg {

    bgVersion = '0.0.1';

    constructor() {
        this.contentScriptCacheInit()
    }

    /** 判断是否是非法页面 */
    IsIllegalPages = (url = '') => {
        const illegal = [
            'chrome://',
            'edge://',
            'about:',
            'moz-extension://',
            'res://',
            'view-source:',
            'devtools://',
            'https://ntp.msn.cn',
            'https://devtools.azureedge.net',
            'chrome-extension://',
        ];
        const flag = illegal.every((str) => !url.startsWith(str));
        return !flag;
    }

    getCurrentTab = async () => {
        const queryOptions = { active: true };
        const [tab] = await chrome.tabs.query(queryOptions);
        return tab;
    }

    // 页面注入contentScript
    contentScriptCacheInit = async () => {
        // 将window._F注入到所有页签中，注入的func会立即执行
        chrome.tabs.query({}, (tabs) => {
            tabs.forEach(async (tab) => {
                try {
                    if (tab.status === 'complete' && !this.IsIllegalPages(tab.url)) {

                        const isInjected = await chrome.scripting
                            .executeScript({
                                target: { tabId: tab.id },
                                func: () => {
                                     console.log(window.co)
                                    return !!window.co
                                },
                                args: []
                            })
                        console.log(isInjected,'isInjected')         
                        if (!isInjected[0].result) {
                            const contentScriptCache = window.pluginBg.contentScriptCache;
                            const { func } = await window.pluginBg.generateFunc(contentScriptCache);
                            await chrome.scripting
                                .executeScript({
                                    target: { tabId: tab.id, allFrames: true },
                                    func,
                                    args: []
                                })
                        }
                    }
                } catch (error) {
                    console.error(error)
                }
            });
        });

        // 更新注入，注入的func会立即执行
        chrome.webNavigation.onCompleted.addListener(async (details) => {
            console.log('onCompleted')
            try {
                const { tabId, url } = details;
                if (!this.IsIllegalPages(url)) {
                    const contentScriptCache = window.pluginBg.contentScriptCache;
                    const { func } = await window.generateFunc(contentScriptCache);
                    await chrome.scripting
                        .executeScript({
                            target: { tabId: tabId, allFrames: true },
                            func
                        })
                }
            } catch (error) {
                console.error(error)
            }
        });
    }

    getRect = async () => {
        const tab = await this.getCurrentTab();
        console.log(tab, 'tab')
        if (!tab) return

        const res = await chrome.scripting
            .executeScript({
                target: { tabId: Number(tab.id) },
                func: async(action) => {
                    const res =await window.co.invoke(action);
                    console.log(res,'res');
                    return {
                        status: 'success',
                        result: typeof res === 'string' ? res : JSON.stringify(res)
                    };
                },
                args: [{
                    type: 'getRect',
                    payload: {
                        xpath: '//body'
                    }
                }]
            })
        console.log(res, '000000000000')
    }
}

window.bg = new Bg();
    `;
    contentScriptCache = `
    console.log('injected');
if (!window.isInjected) {
    window.isInjected = true;
    class Co {

        contentVersion = '0.0.2';

        getElementsByXpath = (xpathExpression) => {
            const xpathResult = document.evaluate(
                xpathExpression,
                document,
                null,
                XPathResult.ANY_TYPE,
                null,
            );
            let element = xpathResult.iterateNext();
            const elements = []
            while (element) {
                elements.push(element)
                element = xpathResult.iterateNext();
            }
            return elements;
        }

        actions = {
            getRect: (payload) => {
                if (payload.xpath) {
                    const [element] = this.getElementsByXpath(payload.xpath);
                    const rect = element.getBoundingClientRect();
                    return {
                        isSuccessed: true,
                        value: JSON.stringify(rect),
                    }
                }
                return {
                    isSuccessed: false,
                    value: null
                }
            }
        }

        invoke = (action) => {
            return new Promise((resolve) => {
                setTimeout(() => {
                    const res = this.actions[action.type](action.payload)
                    console.log(res,'invoke res')
                    resolve(res)
                }, 2000)
            })
        }
    }
    window.co = new Co()
}
    `
    backgroundUrl = chrome.runtime.getURL('plugin/background.html')
    currentTargetId = null; // debugger操作的target

    async unregisterAllDynamicContentScripts() {
        try {
            const scripts = await chrome.scripting.getRegisteredContentScripts();
            const scriptIds = scripts.map(script => script.id);
            return chrome.scripting.unregisterContentScripts(scriptIds);
        } catch (error) {
            const message = [
                "An unexpected error occurred while",
                "unregistering dynamic content scripts.",
            ].join(" ");
            throw new Error(message, { cause: error });
        }
    }

    // 在background页面执行Runtime.evaluate
    bgEvaluate = async (expression) => {
        let targets = await chrome.debugger.getTargets();
        let target = targets.find((target) => target.url === this.backgroundUrl);
        if (this.currentTargetId !== target.id) {
            // target改变了
            if (this.currentTargetId && targets.find(item => item.id === this.currentTargetId)) {
                // 切换前先与当前目标断开
                await chrome.debugger.detach(
                    {
                        targetId: this.currentTargetId
                    }
                )
            }
            await chrome.debugger.attach(
                {
                    targetId: target.id
                },
                '1.2'
            )
            this.currentTargetId = target.id; // 当前targetId
        }

        const { result } = await chrome.debugger.sendCommand(
            {
                targetId: target.id
            },
            'Runtime.evaluate',
            {
                expression,
                returnByValue: true, // 是否返回json数据
                awaitPromise: true, // 是否等待结果 false返回{} true返回[]
            }
        );
        return result
    }

    // 在页面注入代码前的准备工作
    // background页面执行代码生成一个函数
    // 为了能使用chrome.scripting.executeScript注入到所有frame中
    generateFunc = async (code) => {
        try {
            const funcName = `funcName_${Date.now()}`;
            const expression = `(() => {window.${funcName}=async(...arg)=>{${code}}})();`; // 生成一个函数，稍后删除    
            const result = await this.bgEvaluate(expression)
            const getFunc = (() => {
                const func = window[funcName];
                if (func) {
                    delete window[funcName];
                }
                return func
            })
            const res = { ...result, func: getFunc() }
            console.log('generateFunc result:', res)
            return res
        } catch (error) {
            console.error(error)
        }
    }

    /** 基础指令 */

    // 插件初始化
    init = async () => {
        // const targets = await chrome.debugger.getTargets();
        // console.log(targets, 'targets')
        if (__DEV__) {
            await import('../source/backgroundCache.js');
        } else {
            this.backgroundCacheInit()
        }

        // const target = targets.find(target => target.type === 'worker');
        // await chrome.debugger.attach({ targetId: target.id }, '1.2')
        // const res = await chrome.debugger.sendCommand(
        //     { targetId: target.id },
        //     'Runtime.evaluate',
        //     {
        //         expression: '1+1',
        //         returnByValue: true, // 是否返回json数据
        //         awaitPromise: true, // 是否等待结果 false返回{} true返回[]
        //     },
        // );
        // console.log(res, 'res')
    }

    // 注入backgroundCache
    // 在backgroun页面中注入代码
    // 只需要使用debugger Runtime.evaluate注入即可
    // 不能使用注入content的办法在background页注入代码，因为限制了使用chrome.scripting.executeScript
    backgroundCacheInit = async () => {

        this.bgEvaluate(this.backgroundCache)

        // chrome页面无法调用chrome.scripting.executeScript
        // error !!!
        // const tab = await chrome.tabs.getCurrent()
        // await chrome.scripting
        //     .executeScript({
        //         target: { tabId: tab.id, allFrames: true },
        //         func: () => {
        //             new Function('return 0')()
        //             eval('1+1')
        //         },
        //         args: []
        //     })

    }

    getCurrentTab = async () => {
        const queryOptions = { active: true };
        const [tab] = await chrome.tabs.query(queryOptions);
        return tab;
    }


    getRect = async () => {
        const tab = await this.getCurrentTab();
        console.log(tab, 'tab')
        if (!tab) return
        const tabId = tab.id;
        const frameId = 0;
        const func = (originalMethod, params) => {
            console.log(window.invoke, 'window.invoke')
            return window.invoke && window.invoke(originalMethod, params);
        }
        const args = ['getRect', {}];
        const executionWorld = 'ISOLATED'
        const injectionDetails = {
            target: {
                tabId,
                frameIds: [frameId]
            },
            func,
            args,
            world: executionWorld,
            // injectImmediately: injectImmediately chrome 102 之后才有此参数，目前暂不添加
        };
        const res = await chrome.scripting
            .executeScript(injectionDetails)
        console.log(res, '000000000000')
        if (res[0].status === 'needInit') {

            const contentScriptCache = this.contentScriptCache;
            const { func } = await this.generateFunc(contentScriptCache);
            await chrome.scripting
                .executeScript({
                    target: { tabId, frameIds: [frameId] },
                    func,
                    args: []
                })

        }
    }

    getRectByCode = async () => {
        const tab = await this.getCurrentTab();
        console.log(tab, 'tab')
        if (!tab) return
        const code = `
                    const res = await window.co.invoke(arg[0]);
                    console.log(res, 'res');
                    return {
                        status: 'success',
                        result: typeof res === 'string' ? res : JSON.stringify(res)
                    };
        `
        const { func } = await this.generateFunc(code);
        const res = await chrome.scripting
            .executeScript({
                target: { tabId: tab.id, allFrames: true },
                func,
                args: [{
                    type: 'getRect',
                    payload: {
                        xpath: '//body'
                    }
                }]
            })
        console.log(res, '000000000000')
    }

    constructor() {
        chrome.runtime.onInstalled.addListener(() => {
            // 插件安装，更新
            this.unregisterAllDynamicContentScripts()
        });
        chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
            console.log('[service_worker] 收到消息：', message, sender, sendResponse)
        });
        // 与target断开了
        chrome.debugger.onDetach.addListener(
            (source, reason) => {
                this.currentTargetId = null;
                console.log(source, reason)
            }
        )
    }

}

window.pluginBg = new PluginBg();